package jupiter.filters;
import java.sql.SQLException;

import jupiter.channel.*;
import jupiter.message.Message;
import jupiter.util.Application;
import jupiter.util.DBConnection;
import jupiter.util.MemCache;

import org.json.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.ArrayList;
/**
 * @author Jupiter IT
 * Handles Channel requests
 */

public class ChannelFilter {
	static Logger logger = LoggerFactory.getLogger(ChannelFilter.class);
	
	
	/**
	 * Creates a new Channel.
	 * 
	 * @param message a create channel message
	 * <pre>{
	 *     type : "channel",
	 *     action : "create",
	 *     to : "" -> Ignored
	 *     data : {
	 *          subscriptions: ["yourdomain.com/@jabbify.com"],
	 *          address: "yourdomain.com/users/JustinMeyer@jabbify.com", //
	 *          secret : "SECRET KEY" -> optional,
	 *          name : "Justin Meyer" -> optional
	 *          keypList : true | false -> if you should keep a list of subscribers
	 *          publicInfo : {
	 *             website: "hankshomepage.com"
	 *          }
	 *     }
	 * }</pre>
	 * @return if successful returns the channel's data with any optional params filled in
	 * <pre>     
	 *    {
	 *          subscriptions: ["yourdomain.com/@jabbify.com"],
	 *          address: "yourdomain.com/users/JustinMeyer@jabbify.com", //
	 *          secret : "SECRET KEY",
	 *          name : "Justin Meyer"
	 *          publicInfo : {
	 *             website: "hankshomepage.com"
	 *          }
	 *     }</pre>
	 * @throws FilterException
	 */
	public JSONObject create(Message message) throws FilterException{
		return new Channel(message).toJSON(true);
	}

	public JSONObject destroy(Message message) throws FilterException{
		return Channel.remove(message);
	}


	/**
	 * @param message
	 * @return
	 * @throws FilterException
	 */
	public JSONObject getMessages(Message message) throws FilterException {
		String results = null;
		JSONArray results_arr = null;
		results = null; //(String) MemCache.mcc.get("messages:"+message.to().toString());
		if(results == null){
			try{
				results_arr = (JSONArray) (new DBConnection() {
					public Object run(Object o) throws SQLException{
						Message message = (Message) o;
						String type = "message";
						String action = "create";
						String to = message.to().toString();
						
						prepareStatement("Select id, json, created_at from messages WHERE "+
									"`to` = ? AND `type` = ? AND `action` = ?"+
									" ORDER BY id DESC LIMIT 40;");
						pstmt.setString(1, to);
						pstmt.setString(2, type);
						pstmt.setString(3, action);
						executeQuery();
						JSONArray results = new JSONArray();
						while(rs.next()){
							try{
								JSONObject j = new JSONObject( rs.getString("json") );
								j.put("id", rs.getString("id"));
								j.put("createdAt", rs.getTimestamp("created_at").getTime()   ); //give number of MS
								results.put(j);
							}catch(JSONException e){
								ChannelFilter.logger.error("JSON Error in ChannelFilter",e);
							}
						}
						return results;
						
					}
				}).exec(message);
				results = results_arr.toString();
//				MemCache.mcc.set("messages:"+message.to().toString(), results);
			}catch(SQLException sqle){
				ChannelFilter.logger.error("SQL Error in ChannelFilter",sqle);
			}
		} else {
			 try {
				results_arr = new JSONArray(results);
			} catch (JSONException e) {
				ChannelFilter.logger.error("JSON Error creating json array",e);
			}
		}
		JSONObject result = new JSONObject();
		
		try{
			result.put("messages", results_arr);
		}catch(JSONException e){
			ChannelFilter.logger.error("JSON Error with messages",e);
		}
		throw new FilterException(result); //don't send this out
	}
	
	//handles channels being attached
	public JSONObject addListenerInbox(Message message) throws FilterException{
		if(Application.isJabbifyNode()) return null; //just sends the message
		
		
		List n = Global.getDomain(message.to()).getOrCreateChannelList(message.to());
		n.addData(message.data);

		throw new FilterException(); //avoids sending to inboxes
	}
	//handles channels being un-attached
	public JSONObject removeListenerInbox(Message message) throws FilterException{
		if(Application.isJabbifyNode()) return null;
		
		List n = Global.getDomain(message.to()).getOrCreateChannelList(message.to());
		int count = n.removeData(message.data);
		System.out.println("  NF:removeListenerInbox "+count);
		throw new FilterException(); //avoids sending to inboxes
	}
	//returns who is listening
	public JSONObject getSubscribers(Message message) throws FilterException{
		if(Application.isJabbifyNode()) throw new FilterException();
		//send a wakeup call to 
		System.out.println("  CF:getSubscribers "+message.to().toString());
		ArrayList<JSONObject> arr = Global.getDomain(message.to()).getOrCreateChannelList(message.to()).getList();
		
		JSONObject result = new JSONObject();
		try{
			JSONArray jarr = new JSONArray();
			for( JSONObject d : arr){
				jarr.put(d);
			}
			result.put("channels", jarr);
		}catch(JSONException e){
			ChannelFilter.logger.error("JSON Error in getSuscribers",e);
		}
		//convert message
		//message.to( new Address(message.from().toString()) );
		//message.type("session");
		//message.type("break");
		//return result;
		throw new FilterException(result);
	}
	public JSONObject getCount(Message message) throws FilterException{
		if(Application.isJabbifyNode()) throw new FilterException();
		//send a wakeup call to 
		//System.out.println("  CF:getCount!@!*(#%^(*@&^#*(@ "+message.to().toString());
		
		int count = Global.getDomain(message.to()).getOrCreateChannelList(message.to()).getCount();
		
		JSONObject result = new JSONObject();
		try{
			result.put("count", count);
		}catch(JSONException e){
			ChannelFilter.logger.error("JSON Error with count",e);
		}
		//convert message
		//message.to( new Address(message.from().toString()) );
		//message.type("session");
		//message.type("break");
		//return result;
		throw new FilterException(result);
	}
	
	
	
	public JSONObject subscribe(Message message) throws FilterException {
		//convert message to update message with correct data
		//get channels we want to subscribe to
		Address subscriptionAddress;
		Address address = message.address();
		try{
			subscriptionAddress = new Address(message.data.getString("channel"));
		}catch(JSONException e){
			throw new FilterException("{error: 'no channel provided'}");
		}
		//get who we are updating
		Domain dn = Global.getDomain(address);
		ChannelInterface channel = dn.getOrCreateChannel(address); //get or create b/c might not be session
		
		JSONObject newData = channel.subscribeAndUpdate(subscriptionAddress);
		
		//prepare return dat
		JSONObject returnData = new JSONObject();
		ChannelInterface subscribed = dn.getOrCreateChannel(subscriptionAddress);
		try{
			returnData.put("subscribedChannel", subscribed.toJSON(false));
		}catch(JSONException e){
			throw new FilterException("{error: 'error with json'}");
		}
		
		if(newData != null){ //means actual changes
			message.action("update");
			try{
				message.json.put("data", newData);
				returnData.put("subscribingChannel", newData);
			}catch(JSONException e){
				throw new FilterException("{error: 'huh'}");
			}
			//throw new FilterException(returnData);
			return returnData;
		}else{
			try{
				returnData.put("warning", "already subscribed");
				returnData.put("subscribingChannel", channel.toJSON(false));
			}catch(JSONException e){ throw new FilterException("{error: 'huh'}"); }
			throw new FilterException(returnData);
		}

	}
	
	public JSONObject unsubscribe(Message message) throws FilterException {
		//convert message to update message with correct data
		//get channels we want to subscribe to
		Address subscriptionAddress;
		Address address = message.address();
		try{
			subscriptionAddress = new Address(message.data.getString("channel"));
		}catch(JSONException e){
			throw new FilterException("{error: 'no channel provided'}");
		}
		//get who we are updating
		Domain dn = Global.getDomain(address);
		ChannelInterface channel = dn.getOrCreateChannel(address); //get or create b/c might not be session
		
		JSONObject newData = channel.unsubscribeAndUpdate(subscriptionAddress);
		
		//prepare return dat
		JSONObject returnData = new JSONObject();
		ChannelInterface subscribed = dn.getOrCreateChannel(subscriptionAddress);
		try{
			returnData.put("subscribedChannel", subscribed.toJSON(false));
		}catch(JSONException e){
			throw new FilterException("{error: 'error with json'}");
		}
		
		if(newData != null){ //means actual changes
			message.action("update");
			try{
				message.json.put("data", newData);
				returnData.put("subscribingChannel", newData);
			}catch(JSONException e){
				throw new FilterException("{error: 'huh'}");
			}
			//throw new FilterException(returnData);
			return returnData;
		}else{
			try{
				returnData.put("warning", "already unsubscribed");
				returnData.put("subscribingChannel", channel.toJSON(false));
			}catch(JSONException e){ throw new FilterException("{error: 'huh'}"); }
			throw new FilterException(returnData);
		}
	}
	/**
	 * 
	 * @param message
	 * @return
	 * @throws FilterException
	 */
	public JSONObject update(Message message) throws FilterException{
		//find current channel. if address changes, move channel
		Address address = message.address();

		Domain dn = Global.getDomain(address);
		ChannelInterface channel = dn.getOrCreateChannel(address);
		//we should check that the from can change the channel
		return channel.update(message.data);
	}
	
	/**
	 * updates a channels publicInfo
	 * @param message <pre>Whatever public data (minus the address) you want changed.
	 */
	public JSONObject updatePublicInfo(Message message) throws FilterException{
		Address address = message.address();

		Domain dn = Global.getDomain(address);
		ChannelInterface channel = dn.getOrCreateChannel(address);
		//we should check that the from can change the channel
		JSONObject returnData = null;
		try{
			returnData = channel.updatePublicInfo(message.data); //returns what it will be
			//should eventually check if there is newData and do nothing if not
			//convert to update message
			message.action("update");
			
			message.json.put("data", returnData);

		}catch(JSONException e){
			throw new FilterException("{error: 'huh'}");
		}

		return returnData;
	}
	
	public JSONObject updateInbox(Message message) throws FilterException{
		//now if you have the channel, actually update its data, do differently depending if ChannelLIst or not
		Address address = message.address();
		JSONArray arr = null;
		try{
			arr =  message.data.getJSONArray("subscriptions");  //goes to each domain it is subscribed to, and updates it
		}catch(JSONException e){
			ChannelFilter.logger.error("JSON Error in update",e);
		}
//		System.out.println("  NF:updateInbox");
		if(Application.isJabbifyNode()){ //update your channel
			Domain dn = Global.getDomain(address);
			ChannelInterface channel = dn.getChannel(address);
			if(channel != null){
				channel.modify(message.data);
			}
			
		}else{ //update your data
			
			//update everyone's onlist
			int length = arr.length();
			//first lets get the domain
			try{
				//Address add = new Address((String)arr.get(0));
				Address cla =  message.address();
				Domain dn = Global.getDomain(address);
				for(int i =0; i < length; i++){
					
					List n = dn.getChannelList( new Address(arr.getString(i))  );
					if(n != null) {
						JSONObject pi = message.data.getJSONObject("publicInfo");
						pi.put("address", cla.toString());
						n.updateData(pi);
					}
				}
			}catch(JSONException e){
				ChannelFilter.logger.error("JSON Error",e);
			}
			
			
			throw new FilterException(); //there are no inboxes
			
		}
		return null;
	}
}
